/*
 *  fileTransferer.cpp
 *  
 *
 *  Created by B. Jones on 14/01/2010.
 *  Copyright 2010 __MyCompanyName__. All rights reserved.
 *
 */

#include "fileTransferer.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <unistd.h>
#include <stdio.h>
#include "theEncryptor.h"
#include "comms.h"
#include <openssl/blowfish.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include "eventStore.h"


bool fileExists(string strFilename) { 
	struct stat stFileInfo; 
	bool blnReturn; 
	int intStat; 
	
	// Attempt to get the file attributes 
	intStat = stat(strFilename.c_str(),&stFileInfo); 
	if(intStat == 0) { 
		// We were able to get the file attributes 
		// so the file obviously exists. 
		blnReturn = true; 
	} else { 
		// We were not able to get the file attributes. 
		// This may mean that we don't have permission to 
		// access the folder which contains this file. If you 
		// need to do that level of checking, lookup the 
		// return values of stat which will give you 
		// more details on why stat failed. 
		blnReturn = false; 
	} 
	
	return(blnReturn); 
}

int setNonblocking(int fd)
{
    int flags;
	
    /* If they have O_NONBLOCK, use the Posix way to do it */
#if defined(O_NONBLOCK)
    /* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
    if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
        flags = 0;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#else
    /* Otherwise, use the old way of doing it */
    flags = 1;
    return ioctl(fd, FIOBIO, &flags);
#endif
}

int setBlocking(int fd)
{
	int flags;
	if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
        flags = 0;
    return fcntl(fd, F_SETFL, flags);
}

int length(const char* temp)
{
    int count = 0;
	
    while (temp[count] != 0)
		++count;
	
    return count;
}


using namespace std;

fileTransferer::fileTransferer(){}

void
fileTransferer::send_file(int sock, const char* fullPath)
{
	
	
	//std::cout<<"Sending file..."<<fullPath<<std::endl;
	
	int fd;
	struct stat stat_buf;
	off_t offset = 0;
	
	int n;
	
	/* Open the input file. */
	fd = open ((const char*)fullPath, O_RDONLY);
	
	/* Stat the input file to obtain its size. */
	fstat (fd, &stat_buf);	
	std::ostringstream ostr;
	int size = stat_buf.st_size;
	ostr << size;
	std::string str(ostr.str());
	char strs[64]={'0'};
	strncpy ( strs, str.c_str(), strlen(str.c_str()) );
	
	//std::cout<<"strs send: "<<strs<<std::endl;
	
	/* send file size */
	n=send(sock,strs,64,0);
	
	/* get ack */
	char resp[10];
	bzero(resp,10);
	recv(sock, resp, 2, 0 );
	

	bool broken=false;
	
	//std::cout<<resp<<std::endl;
	

	/* proceed to send file */
	double remSize=size;
	double value=0;
	char buffer[2054];

	while(size >= 0)
	{
		bzero(buffer,2054);
		volatile int n;
		if(size>=2048)
		{
			value+=2048;
			n=read(fd, buffer, 2048);

			comms::encryptor->sendIvec(sock,1);
			comms::encryptor->blowfish((unsigned char*)buffer,2048,BF_ENCRYPT,1);

			n=send(sock, buffer, 2048, 0 );
			
			while(n<2048)
			{
				char fill[1];
				fill[0]=buffer[n];
				n+=send(sock,fill,1,0);
			}
		}
		else // reamining file bytes
		{
			value+=size;
			n=read(fd, buffer, size);
			buffer[size]='\0';

			comms::encryptor->sendIvec(sock,1);
			comms::encryptor->blowfish((unsigned char*)buffer,size,BF_ENCRYPT,1);

			n=send(sock,buffer, size, 0 );

			while(n<size)
			{
				char fill[1];
				fill[0]=buffer[n];
				n+=send(sock,fill,1,0);
			}
		}
		
		eventStore es;
		es.type=5;
		es.amount=(double)(value/remSize)*100.0;
		//std::cout<<es.amount<<std::endl;
		pthread_mutex_lock(&listener::mutex);
		listener::eventQueue.push(es);
		pthread_mutex_unlock(&listener::mutex);

		size -= 2048;


	}
	
	if(!broken)
	{
		// Send a completion string
		n = send(sock, "COMP",4, 0 );
		char buf[2];
		bzero(buf,2);
		// Receive an acknowledgemnt
		n = recv(sock, buf, 2, 0 );
	}

	eventStore es;
	es.type=5;
	es.amount=0;
	//std::cout<<es.amount<<std::endl;
	pthread_mutex_lock(&listener::mutex);
	listener::eventQueue.push(es);
	pthread_mutex_unlock(&listener::mutex);

}

// update 24th Jan 2010 -- code now implements non-blocking functionality
// This makes cancelling a file transfer and any problems with file transfer
// more graceful
void
fileTransferer::receive_file(int sock,const char* fname)
{
	
	struct timeval timeout;
	timeout.tv_sec = 10;
	timeout.tv_usec = 0;
	
	fd_set socks;  
	FD_ZERO(&socks);
	FD_SET(sock,&socks);
	setNonblocking(sock);
	
	//std::cout<<"Attempting to receive file..."<<std::endl;
	
	// receive file size and send ack
	char sizeBuffer[64];
	bzero(sizeBuffer,64);
	volatile int n;
	
	bool updateGuage=true;
	
	int readsocks = select(sock+1, &socks, (fd_set *) 0, 
						   (fd_set *) 0, &timeout);

	//std::cout<<"readSocks A: "<<readsocks<<std::endl;

	if(readsocks>0) // data available on socket, safe to call recv
	{
		n=recv(sock, sizeBuffer, 64,0);
		n=send(sock,"OK", strlen("OK"), 0 );
	
	
		char sb[64]={'0'};
		strncpy(sb,sizeBuffer,strlen(sizeBuffer));
		sb[strlen(sizeBuffer)]='\0';
		
		int size = atoi(sb);

		std::cout<<"size: "<<size<<std::endl;

		timeout.tv_sec = 1;


		if(!fileExists(std::string(fname)))
		{
			std::ofstream outFile(fname,ios::out|ios::binary|ios::app);
			//n=send(sock,"OK",strlen("OK"), 0 );

			// vars for status gauge
			double remSize=size;
			double value=0;
			
			// buffer for storing incoming data
			char buf[2048];
			
			volatile int res=0;
			
			bool broken=false;
			
			int toRead;
			

			while(size > 0)
			{
				
				updateGuage=true;
				/*
				if(!encap::continueReceiving) // cancel sending!
				{
					encap::continueReceiving=true;
					MyFooEvent event( 0, 999 );
					event.colour=wxColour(*wxBLACK);
					//event.SetText( wxString(wxChar(sendBuffer)) );
					wxString recString;
					recString.Append(wxString(wxT("SYSTEM: File transfer cancelled")));
					recString.Append(wxT("\n"));
					event.SetText(recString);
					wxPostEvent (wxid, event);
					broken=true;

					//Since there might still be incoming data on this socket,
					//we need to keep receiving until there is no more data
					//this essentially flushes the socket so that when subsequent data
					//is received, it doesn't begin by receiving this scrap data
					timeout.tv_sec = 0;
					timeout.tv_usec = 10;
					while(select(sock+1, &socks, (fd_set *) 0,
									   (fd_set *) 0, &timeout)>0)
					{
						n=recv(sock, buf, 1, 0 );
					}


					break;
				}*/
				
				bzero(buf,2048);
				if(size>=2048)
				{

					value+=2048; // for status gauge
					
					// receive chunk of data
					int readsocks = select(sock+1, &socks, (fd_set *) 0,
									   (fd_set *) 0, &timeout);

					//std::cout<<"readsocksB: "<<readsocks<<std::endl;

					if(readsocks>0) // data available on socket, safe to call recv
					{
						comms::encryptor->receiveIvec(sock,2);

						readsocks = select(sock+1, &socks, (fd_set *) 0,
															   (fd_set *) 0, &timeout);
						if(readsocks>0)
						{
							n=recv(sock, buf, 2048, 0 );
							size-=n;
							while(n<2048)
							{
								readsocks = select(sock+1, &socks, (fd_set *) 0,
												   (fd_set *) 0, &timeout);

								//std::cout<<"readsocksC: "<<readsocks<<std::endl;

								if(readsocks>0)
								{
									char fill[1]={'0'};
									int readVal=recv(sock,fill,1,0);
									n+=readVal;
									buf[n-1]=fill[0];
									size-=readVal;
								}
							}

							comms::encryptor->blowfish((unsigned char*)buf,2048,BF_DECRYPT,2);

							// write chunk of data to disk
							outFile.write(buf,2048);

						}
						
						
					}else updateGuage=false;
					
				}
				else
				{
					value+=size;
					int readsocks = select(sock+1, &socks, (fd_set *) 0,
										   (fd_set *) 0, &timeout);
					//std::cout<<"readsocksD: "<<readsocks<<std::endl;
					if(readsocks>0) // data available on socket, safe to call recv
					{
						comms::encryptor->receiveIvec(sock,2);
						readsocks = select(sock+1, &socks, (fd_set *) 0,
										   (fd_set *) 0, &timeout);
						if(readsocks>0)
						{
							n=recv(sock, buf, size, 0 );
							size-=n;

							while(n<size)
							{
								readsocks = select(sock+1, &socks, (fd_set *) 0,
												   (fd_set *) 0, &timeout);
								//std::cout<<"readsocksE: "<<readsocks<<std::endl;
								if(readsocks>0)
								{
									char fill[1]={'0'};
									int readVal=recv(sock,fill,1,0);
									n+=readVal;
									buf[n-1]=fill[0];
									size-=readVal;
								}
							}
							comms::encryptor->blowfish((unsigned char*)buf,size,BF_DECRYPT,2);

							outFile.write(buf,size);
						}
					}else updateGuage=false;
				}
				
				eventStore es;
				es.type=6;
				es.amount=(double)(value/remSize)*100.0;
				pthread_mutex_lock(&listener::mutex);
				listener::eventQueue.push(es);
				pthread_mutex_unlock(&listener::mutex);

				// Update status gauge
				/*
				if(updateGuage)
				{
					MyFooEvent event( 0, 990 );
					double firstBit = (double)value/remSize;
					firstBit=firstBit*100.0;
					event.adouble=firstBit;
					wxPostEvent (wxid, event);
				}
				*/
			}

			outFile.close();

			/*
			wxCommandEvent event(0,989);
			wxPostEvent (wxid, event);
			*/


			// now we set the socket back to blocking mode!
			//setBlocking(sock);

			if(!broken)
			{

				// Receive 'COMP' and send acknowledgement
				// ---------------------------------------
				timeout.tv_sec = 10;
				readsocks = select(sock+1, &socks, (fd_set *) 0,
								   (fd_set *) 0, &timeout);
				//std::cout<<"readsocksF: "<<readsocks<<std::endl;
				if(readsocks>0)
				{
					char bufb[4];
					bzero(bufb,4);
					n = recv(sock, bufb, 4, 0 );
					n = send(sock,  "OK", 2, 0 );
				}
				
				
				//std::cout<<"File received..."<<std::endl;
				//std::cout<<"received..."<<std::endl;
			}

		}
		else // file already exists so don't send OK
		{
			n=send(sock,"KO",strlen("KO"), 0 );
		}

	}

	std::cout<<"finished file receive! "<<std::endl;
	eventStore es;
	es.type=6;
	es.amount=0;
	//std::cout<<es.amount<<std::endl;
	pthread_mutex_lock(&listener::mutex);
	listener::eventQueue.push(es);
	pthread_mutex_unlock(&listener::mutex);
}

void
fileTransferer::fileEncryptor(const char* filename)
{
	int read_fd;
	struct stat stat_buf;
	off_t offset = 0;
	
	/* Open the input file. */
	read_fd = open (filename, O_RDONLY);
	
	/* Stat the input file to obtain its size. */
	fstat (read_fd, &stat_buf);	
	std::ostringstream ostr;
	int size = stat_buf.st_size;
}

fileTransferer::~fileTransferer(){}


